package optimizers.ganeatgeneric;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.JTextPane;

import optimizers.commons.GAIndiv;
import optimizers.commons.RunConfig;
import optimizers.commons.Static;
import runconfiguration.SinConfiguration;

import model.OligoSystemGeneral;

import util.MyUtil;
import visualization.PlotExpData;
import fitness.FitnessResult;

public class Individual extends GAIndiv implements Cloneable, Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	public ArrayList<Node> nodeGenes = new ArrayList<Node>();
	public ArrayList<Connection> connectionGenes = new ArrayList<Connection>();
	public Map<String, Integer> innovations;
	public Map<String, String> nodeNames;
	public Individual parent1;
	public Individual parent2;
	public int generationIndex;
	public int individualIndex;
	public String mutationInfo = "Not mutated";
	public String modelString = "";
	public Individual spicies;
	public Individual indivCompared;
	public double exo;
	public double pol;
	public double nick;

	protected Random rand = new Random();
	
	public Individual(double[] genes, int nbOfSimpleSequences, RunConfig config, HashMap<String,Object> annotations) {
		super(genes,nbOfSimpleSequences,config,annotations);
		this.nametoint = new HashMap<String,Integer>();
		this.inttoname = new HashMap<Integer,String>();
		this.innovations = (Map<String,Integer>) annotations.get("innovations");
		this.nodeNames = (Map<String,String>) annotations.get("nodeNames");
		this.generationIndex = ((Integer) annotations.get("generationIndex"));
		this.individualIndex = ((Integer) annotations.get("individualIndex"));
		this.config = config;
		if(annotations.get("text")!=null){
			evalText((String) annotations.get("text"));
		}
	}

	public void evalText(String text) {
		String[] lines = text.split("\r\n");
		int mode = 0;
		for (int i = 0; i < lines.length; i++) {
			if (lines[i].contains("Node gens:")) {
				mode = 1;
				continue;
			} else if (lines[i].contains("Connection gens:")) {
				sanitizeNodes();
				mode = 2;
				continue;
			} else if (lines[i].contains("Exo:")) {
				mode = 3;
			} else if (lines[i].contains("Pol:")) {
				mode = 4;
			} else if (lines[i].contains("Nick:")) {
				mode = 5;
			}
			
			switch (mode) {
			case 1:
				String[] tmp1 = lines[i].trim().split(" ");
				nodeGenes.add(new Node(tmp1[0], Double.valueOf(tmp1[1]), Double
						.valueOf(tmp1[2]),
						tmp1[0].startsWith("I") ? Node.INHIBITING_SEQUENCE
								: Node.SIMPLE_SEQUENCE));
				break;
			case 2:
				String[] tmp2 = lines[i].trim().split(" ");
				String[] nodes = tmp2[1].split("->");
				addConnection(getNode(nodes[0]), getNode(nodes[1]),
						tmp2[2].equals("true"), Double.valueOf(tmp2[3]));
				break;
			case 3:
				String[] tmp3 = lines[i].trim().split(" ");
				exo = Double.valueOf(tmp3[1]);
				break;
			case 4:
				String[] tmp4 = lines[i].trim().split(" ");
				pol = Double.valueOf(tmp4[1]);
				break;
			case 5:
				String[] tmp5 = lines[i].trim().split(" ");
				nick = Double.valueOf(tmp5[1]);
				break;
			}
		}
	}

	protected void sanitizeNodes() {
		Node node = null;
		int count = 0;
		for(int i=0; i<this.nodeGenes.size(); i++){
			node = nodeGenes.get(i);
			if(node.type == Node.SIMPLE_SEQUENCE){
			nametoint.put(node.name, count); //TODO: not sure, have to check if correct representation
			inttoname.put(count, node.name); //It's fine if all the simple seqs are defined first, though
			count++;
			}
		}
		if(!annotations.get("nbSimpleSequences").equals(count)){
			System.err.println("Possible discrepency among values");
			annotations.put("nbSimpleSequences", count);
		}
		int size = count*count*(count+1); //size of the templates
		
		if(this.config.seqK.optimizeMechanism == RunConfig.SEQK_OPTIMIZE_YES){
			size += count;
		}
		if(this.config.inhK.optimizeMechanism == RunConfig.INHK_OPTIMIZE_YES){
			size += count*count;
		}
		if(this.config.seqC.optimizeMechanism == RunConfig.SEQC_OPTIMIZE_YES){
			size += count*(count+1);
		}
		if(this.config.exoActivity.optimizeMechanism == RunConfig.EXO_OPTIMIZE_YES){
			size += 1;
		}
		if(this.config.polActivity.optimizeMechanism == RunConfig.EXO_OPTIMIZE_YES){
			size += 1;
		}
		if(this.config.nickActivity.optimizeMechanism == RunConfig.EXO_OPTIMIZE_YES){
			size += 1;
		}
		if(this.config.exoSimpleKm.optimizeMechanism == RunConfig.EXO_OPTIMIZE_YES){
			size += 1;
		}
		if(this.config.exoInhibKm.optimizeMechanism == RunConfig.EXO_OPTIMIZE_YES){
			size += 1;
		}
		if(this.config.polKm.optimizeMechanism == RunConfig.EXO_OPTIMIZE_YES){
			size += 1;
		}
		if(this.config.polDoubleStrandKm.optimizeMechanism == RunConfig.EXO_OPTIMIZE_YES){
			size += 1;
		}
		if(this.config.nickKm.optimizeMechanism == RunConfig.EXO_OPTIMIZE_YES){
			size += 1;
		}
		
		if(this.genes.length != size){
			this.genes = new double[size];
		}
		
		int where = 0;
		if(this.config.seqK.optimizeMechanism == RunConfig.SEQK_OPTIMIZE_YES){
			for(int i=0; i<count; i++){
				genes[i] = this.getNode(inttoname.get(i)).parameter;
			}
			where += count;
		}
		if(this.config.inhK.optimizeMechanism == RunConfig.INHK_OPTIMIZE_YES){
			Node inh = null;
			for(int i=0; i<count; i++){
				for(int j=0; j<count; j++){
					inh = this.getNode("I"+inttoname.get(i)+inttoname.get(j));
					if(inh != null){
						genes[where+i*count+j] = inh.parameter;
					}
				}
			}
			where += count*count;
		}
		if(this.config.seqC.optimizeMechanism == RunConfig.SEQC_OPTIMIZE_YES){
			Node tmp = null;
			for(int i=0; i<count; i++){
				genes[where+i] = this.getNode(inttoname.get(i)).concentration;
			}
			where += count;
			for(int i=0; i<count; i++){
				for(int j=0; j<count; j++){
					tmp = this.getNode("I"+inttoname.get(i)+inttoname.get(j));
					if(tmp != null){
						genes[where+i*count+j] = tmp.concentration;
					}
				}
			}
		}
	}

	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder();
		builder.append("Node gens:\n");
		for (Node node : nodeGenes) {
			double parameter = node.parameter;
			if (node.type == Node.INHIBITING_SEQUENCE) {
				for (Node tmp1 : this.nodeGenes) {
					for (Node tmp2 : this.nodeGenes) {
						if (node.name.equals("I" + tmp1.name + tmp2.name)) {
							parameter = MyUtil.calculateInhK(tmp1.parameter,
									tmp2.parameter);
						}
					}
				}
			}
			builder.append(" " + node.name + " " + Static.df4.format(parameter)
					+ " " + Static.df4.format(node.concentration) + "\n");
		}
		builder.append("Connection gens:\n");
		for (Connection connection : connectionGenes) {
			builder.append(" " + connection.innovation + " "
					+ connection.from.name + "->" + connection.to.name + " "
					+ connection.enabled + " "
					+ Static.df4.format(connection.parameter) + "\n");
		}
		builder.append("Exo: " + Static.df4.format(exo) + "\n");
		builder.append("Pol: " + Static.df4.format(pol) + "\n");
		builder.append("Nick: " + Static.df4.format(nick) + "\n");
		return builder.toString();
	}

	protected int getTemplatesOffset(){
		int offset = 0;
		int nbSimpleSequences = ((Integer) annotations.get("nbSimpleSequences"));
		if(this.config.seqK.optimizeMechanism == RunConfig.SEQK_OPTIMIZE_YES)
			offset += nbSimpleSequences;
		if(this.config.inhK.optimizeMechanism == RunConfig.INHK_OPTIMIZE_YES)
			offset += nbSimpleSequences*nbSimpleSequences;
		if(this.config.seqC.optimizeMechanism == RunConfig.SEQC_OPTIMIZE_YES)
			offset += nbSimpleSequences*(nbSimpleSequences+1);
		
		return offset;
	}
	
	public Connection getInhibitedConnection(Node node) {
		if (node.type == Node.INHIBITING_SEQUENCE) {
			for (Connection conn : connectionGenes) {
				if (node.name.equals("I" + conn.from.name + conn.to.name)) {
					return conn;
				}
			}
		} else {
			return null;
		}
		return null;
	}

	public Connection getConnection(int innovation) {
		for (Connection connection : connectionGenes) {
			if (connection.innovation == innovation)
				return connection;
		}
		return null;
	}

	public Node getNode(String nodeName) {
		for (Node node : nodeGenes) {
			if (node.name.equals(nodeName))
				return node;
		}
		return null;
	}

	public int getNEnabledConnections() {
		int result = 0;
		for (Connection conn : connectionGenes) {
			if (conn.enabled) {
				result++;
			}
		}
		return result;
	}

	protected double mutateParam(double oldParam, double min, double max) {
		double mutatedParam;
		if (rand.nextDouble() < Constants.probRandomParamMutate) {
			mutatedParam = MyUtil.randomDoubleLogScale(rand, min, max);
		} else {
			mutatedParam = MyUtil.gaussianTransform(rand, oldParam, min, max,
					Constants.paramMutateGaussianFactor);
		}
		return mutatedParam;
	}

	public String mutateParameter() {
		StringBuilder info = new StringBuilder();
		info.append("Mutate Parameters:\n Nodes: \n");
		int base = 0;
		
		if (config.seqK.optimizeMechanism == RunConfig.SEQK_OPTIMIZE_YES) {
			base = ((Integer) annotations.get("nbSimpleSequences"));
			info.append("\n K params:\n");
			for (Node node : nodeGenes) {
				if (node.type == Node.SIMPLE_SEQUENCE) {
					if (rand.nextDouble() < Constants.probGeneMutation) {
						double oldParameter = node.parameter;
						node.parameter = mutateParam(node.parameter,
								config.seqK.minValue, config.seqK.maxValue);

						//update genes
						genes[nametoint.get(node.name)] = node.parameter;
						info.append(node.name + ": " + oldParameter + "->"
								+ node.parameter + "\n");
					}
				}
			}
		}
		
		if (config.inhK.optimizeMechanism == RunConfig.INHK_OPTIMIZE_YES) {
			info.append("\n inhK params:\n");
			for (Node node : nodeGenes) {
				if (node.type == Node.INHIBITING_SEQUENCE) {
					if (rand.nextDouble() < Constants.probGeneMutation) {
						double oldParameter = node.parameter;
						
						node.parameter = mutateParam(node.parameter,
								config.inhK.minValue, config.inhK.maxValue);

						//updating the genes
						int where = base+ ((Integer) annotations.get("nbSimpleSequences"))*(nametoint.get(node.inhibfrom)) + nametoint.get(node.inhibto);

						genes[where] = node.parameter;
						info.append(node.name + ": " + oldParameter + "->"
								+ node.parameter + "\n");
					}
				}
			}
			base += ((Integer) annotations.get("nbSimpleSequences"))*((Integer) annotations.get("nbSimpleSequences"));
		}

		if (config.seqC.optimizeMechanism == RunConfig.SEQC_OPTIMIZE_YES) {
			int where;
			info.append(" Concentrations:\n");
			for (Node node : nodeGenes) {
				if (rand.nextDouble() < Constants.probGeneMutation) {
					double oldParameter = node.concentration;
					node.concentration = mutateParam(node.concentration,
							config.seqC.minValue, config.seqC.maxValue);
					
					//updating genes
					if(node.type == Node.SIMPLE_SEQUENCE){
						where = base + nametoint.get(node.name);
					} else {
						where = base + ((Integer) annotations.get("nbSimpleSequences"))*(1+nametoint.get(node.inhibfrom)) +nametoint.get(node.inhibto);
					}
					genes[where] = node.concentration;
					info.append(node.name + ": " + oldParameter + "->"
							+ node.concentration + "\n");
				}
			}
			base += ((Integer) annotations.get("nbSimpleSequences"))*((Integer) annotations.get("nbSimpleSequences") + 1);
		}
		
		if (config.template.optimizeMechanism == RunConfig.TEMPLATE_OPTIMIZE_YES) {
			info.append("\n Connections: \n");
			for (int i = connectionGenes.size() - 1; i >= 0; i--) {
				Connection connection = connectionGenes.get(i);
				boolean subZero = false;
				if (connection.enabled
						&& rand.nextDouble() < Constants.probGeneMutation) {
					int firstrank = connection.to.type == Node.SIMPLE_SEQUENCE ? 0 : nametoint.get(connection.from.name)+1;
					int secondrank = connection.to.type == Node.SIMPLE_SEQUENCE ? nametoint.get(connection.from.name) : nametoint.get(connection.to.inhibfrom);
					int thirdrank = connection.to.type == Node.SIMPLE_SEQUENCE ? nametoint.get(connection.to.name) : nametoint.get(connection.to.inhibto);
					int where = base + firstrank * ((Integer) annotations.get("nbSimpleSequences"))*((Integer) annotations.get("nbSimpleSequences")) + secondrank * ((Integer) annotations.get("nbSimpleSequences")) + thirdrank;
					double oldParameter = connection.parameter;
					boolean disabled = false;
					double newParameter = mutateParam(
							connection.parameter, config.template.minValue,
							config.template.maxValue);
					if ((rand.nextDouble() < MyUtil
							.probDisableConnection(connection.parameter) || Double.isNaN(newParameter))&&getNEnabledConnections() > 1) {
						connection.enabled = false;
						genes[where] = - genes[where]; // negative values means disabled
						disabled = true;
					} else {
						if (Double.isNaN(newParameter)){
							connection.parameter = 0.001;
							genes[where] = 0.001;
						}else{
							connection.parameter = newParameter;
							genes[where] = newParameter;
						}
					}
					if (connection.parameter < 0) {
						subZero = true;
						connection.enabled = false;
						connection.parameter = -connection.parameter;
						//Automatically done with genes
						
						if (connection.to.type == Node.INHIBITING_SEQUENCE) {
							Connection inhibitedConnection = getInhibitedConnection(connection.to);
							if (inhibitedConnection != null) {
								addConnection(connection.from,
										inhibitedConnection.to, true,
										connection.parameter);
								
							}
						} else {
							mutateAddInhibition(connection.from, connection.to,
									config.template.defaultValue);
						}
					}
					if (disabled) {
						info.append(connection.from.name + connection.to.name
								+ ": Disabled\n");
					} else {
						info.append(connection.from.name + connection.to.name
								+ ": " + oldParameter + "->"
								+ (subZero ? "(-)" : "") + connection.parameter
								+ "\n");
					}
				}
			}
		}
		if (config.polActivity.optimizeMechanism == RunConfig.POL_OPTIMIZE_YES) {
			double oldParameter = pol;
			pol = mutateParam(pol, config.polActivity.minValue, config.polActivity.maxValue);
			info.append("\nK pol:\n" + oldParameter + "->" + pol + "\n");
		}
		if (config.exoActivity.optimizeMechanism == RunConfig.EXO_OPTIMIZE_YES) {
			double oldParameter = exo;
			exo = mutateParam(exo, config.exoActivity.minValue, config.exoActivity.maxValue);
			info.append("\nK pol:\n" + oldParameter + "->" + exo + "\n");
		}
		if (config.nickActivity.optimizeMechanism == RunConfig.NICK_OPTIMIZE_YES) {
			double oldParameter = nick;
			nick = mutateParam(nick, config.nickActivity.minValue, config.nickActivity.maxValue);
			info.append("\nK nick:\n" + oldParameter + "->" + nick + "\n");
		}
		mutationInfo = info.toString();
		return mutationInfo;
	}

	public Connection addConnection(Node from, Node to, boolean enabled,
			double parameter) {
		int geneOffset = getTemplatesOffset();
		int innovation = innovations.size() + 1;
		if(innovations != null && innovations.get("a->a") == null){
			//System.out.println("Something wrooooong");
		}
		String key = from.name + "->" + to.name;
		if (innovations.get(key) == null) {
			innovations.put(key, innovation);
		} else {
			innovation = innovations.get(key);
		}
		Connection newConnection = getConnection(innovation);
		if (newConnection == null) {
			newConnection = new Connection(innovation, from, to, enabled,
					parameter);
			connectionGenes.add(newConnection);
		} else {
			newConnection.enabled = true;
			newConnection.parameter = parameter;
		}
		
		//Added to keep the genes coherent
		int nbSimpleSequence = ((Integer) annotations.get("nbSimpleSequences"));
		int firstrank = newConnection.to.type == Node.SIMPLE_SEQUENCE ? 0 : nametoint.get(newConnection.from.name)+1;
		int secondrank = newConnection.to.type == Node.SIMPLE_SEQUENCE ? nametoint.get(newConnection.from.name) : nametoint.get(newConnection.to.inhibfrom);
		int thirdrank = newConnection.to.type == Node.SIMPLE_SEQUENCE ? nametoint.get(newConnection.to.name) : nametoint.get(newConnection.to.inhibto);
		int where = geneOffset + firstrank * nbSimpleSequence*nbSimpleSequence + secondrank * nbSimpleSequence + thirdrank;
		genes[where] = parameter;
		
		return newConnection;
	}

	public Connection getConnection(String fromName, String toName) {
		for (Connection conn : connectionGenes) {
			if (conn.from.name.equals(fromName) && conn.to.name.equals(toName)) {
				return conn;
			}
		}
		return null;
	}

	public String mutateAddActivation() {
		ArrayList<Connection> possibleActivations = new ArrayList<Connection>();
		for (Node from : nodeGenes) {
			if (from.type != Node.INHIBITING_SEQUENCE) {
				for (Node to : nodeGenes) {
					if (getConnection(from.name, to.name) == null) {
						possibleActivations.add(new Connection(0, from, to,
								true, 0));
					}
				}
			}
		}
		if (possibleActivations.size() == 0) {
			mutationInfo = "Cannot add activation";
		} else {
			int index = rand.nextInt(possibleActivations.size());
			Connection connection = possibleActivations.get(index);
			addConnection(connection.from, connection.to, true,
					config.template.defaultValue);
			mutationInfo = "Mutate Add Activation: " + connection.from.name
					+ "->" + connection.to.name;
		}
		return mutationInfo;

		/*
		 * Node from = null; Node to = null; boolean connectionExist = true; int
		 * tries = 0; while (connectionExist && tries < 10) { tries++; from =
		 * null; to = null; while (from == null || from.type ==
		 * Node.INHIBITING_SEQUENCE) { int index =
		 * rand.nextInt(nodeGenes.size()); from = nodeGenes.get(index); } while
		 * (to == null || to.name.equals("i1") || to.name.equals("i2")) { int
		 * index = rand.nextInt(nodeGenes.size()); to = nodeGenes.get(index); }
		 * connectionExist = false; for (Connection connection :
		 * connectionGenes) { if (connection.from.name == from.name &&
		 * connection.to.name == to.name) { connectionExist = true; break; } } }
		 * if (!connectionExist) { addConnection(from, to, true,
		 * Constants.defaultConnectionParam); mutationInfo =
		 * "Mutate Add Activation: " + from.name + "->" + to.name; } else {
		 * mutationInfo = "Full of connections"; } return mutationInfo;
		 */
	}

	public String mutateAddInhibition(Node nodeFrom, Node nodeTo,
			double parameter) {
		int nbSimpleSequence = ((Integer) annotations.get("nbSimpleSequences"));
		int base = this.config.seqK.optimizeMechanism == RunConfig.SEQK_OPTIMIZE_YES ? nbSimpleSequence : 0;
		ArrayList<Connection> possibleConnections = new ArrayList<Connection>();
		for (Connection conn : connectionGenes) {
			if (conn.to == nodeTo && conn.enabled) {
				possibleConnections.add(conn);
			}
		}
		Connection conn = null;
		if (possibleConnections.size() == 0) {
			conn = addConnection(nodeTo, nodeTo, true,
					config.template.defaultValue);
		} else {
			conn = possibleConnections.get(rand.nextInt(possibleConnections
					.size()));
		}
		Node inhibitionNode = new Node("I" + conn.from.name + conn.to.name,
				config.inhK.defaultValue, config.seqC.defaultValue,
				Node.INHIBITING_SEQUENCE, conn.from.name, conn.to.name);
		if (getNode(inhibitionNode.name) == null) {
			nodeGenes.add(inhibitionNode);
			if(this.config.inhK.optimizeMechanism == RunConfig.INHK_OPTIMIZE_YES){
				int where = base + nametoint.get(conn.from.name)*nbSimpleSequence +nametoint.get(conn.to.name);
				genes[where] = config.inhK.defaultValue;
				base += nbSimpleSequence*nbSimpleSequence;
			}
			if(this.config.seqC.optimizeMechanism == RunConfig.SEQC_OPTIMIZE_YES){
				int where = base + (nametoint.get(conn.from.name)+1)*nbSimpleSequence +nametoint.get(conn.to.name);
				genes[where] = config.seqC.defaultValue;
			}
		} else {
			inhibitionNode = getNode(inhibitionNode.name);
		}
		addConnection(nodeFrom, inhibitionNode, true, parameter);
		mutationInfo = "Mutate Add Inhibition: " + nodeFrom.name + "->"
				+ inhibitionNode.name;

		return mutationInfo;

	}

	public String mutateAddInhibition() {
		ArrayList<Node> possibleNodes = new ArrayList<Node>();
		for (Node node : nodeGenes) {
			if (node.type == Node.SIMPLE_SEQUENCE) {
				possibleNodes.add(node);
			}
		}
		if (possibleNodes.size() == 0) {
			return mutateParameter();
		}
		Node nodeFrom = possibleNodes.get(rand.nextInt(possibleNodes.size()));
		Node nodeTo = possibleNodes.get(rand.nextInt(possibleNodes.size()));
		Connection connection = getConnection(nodeFrom.name, nodeTo.name);
		if (connection != null)
			connection.enabled = false;
		return mutateAddInhibition(nodeFrom, nodeTo,
				config.template.defaultValue);

	}

	public void mutateAddConnection() {
		if (rand.nextDouble() < Constants.probAddActivation) {
			mutateAddActivation();
		} else {
			mutateAddInhibition();
		}
	}

	private Connection getBackConnection(Connection connection) {
		Connection result = null;
		int countIn = 0;
		int countOut = 0;
		for (Connection conn : connectionGenes) {
			if (conn.from == connection.from && conn.enabled) {
				countOut++;
			}
			if (conn != connection && conn.to == connection.from
					&& conn.enabled) {
				result = conn;
				countIn++;
			}
		}
		if (countIn == 1 & countOut == 1) {
			return result;
		} else {
			return null;
		}
	}

	protected Connection getCorrectConnection(Connection connection) {
		Connection conn = connection;
		while (getBackConnection(conn) != null
				&& getBackConnection(conn) != connection) {
			conn = getBackConnection(conn);
		}
		return conn;

	}

	public String mutateAddNode() {
		if (nodeGenes.size() == 0) return "Empty individual";
		if (rand.nextDouble() < 1.0f / (getNSimpleSequences() + 1)) {
			// new mutation rule
			if (rand.nextBoolean()) {
				// new node connect to a simple sequence node
				Node to = null;
				while (to == null || to.type == Node.INHIBITING_SEQUENCE) {
					int index = rand.nextInt(nodeGenes.size());
					to = nodeGenes.get(index);
				}
				int i = nodeNames.size() + 99;
				char c = (char) i;
				String nodeName = Character.toString(c);
				String nodeOrigins = "null->" + to.name;
				if (nodeNames.get(nodeOrigins) == null) {
					nodeNames.put(nodeOrigins, nodeName);
				} else {
					nodeName = nodeNames.get(nodeOrigins);
				}
				boolean hasNodeName = true;
				i = 0;
				while (hasNodeName) {
					if (i > 0) {
						nodeName = nodeName + String.valueOf(i);
					}
					hasNodeName = false;
					for (Node node : nodeGenes) {
						if (node.name.equals(nodeName)) {
							hasNodeName = true;
							break;
						}
					}
					i++;
				}
				Node newNode = new Node(nodeName, config.seqK.defaultValue,
						config.seqC.defaultValue, Node.SIMPLE_SEQUENCE);
				nodeGenes.add(newNode);
				//new genome
				this.genes = increasedSequenceNumber();
				
				nametoint.put(nodeName, ((Integer) annotations.get("nbSimpleSequences")-1));
				inttoname.put(((Integer) annotations.get("nbSimpleSequences")-1), nodeName);
				addConnection(newNode, to, true, config.template.defaultValue);
				addConnection(newNode, newNode, true,
						config.template.defaultValue);
				mutationInfo = "Mutate Add Node :" + newNode.name + "->"
						+ to.name;
			} else {
				// new node connect to inhibiting sequence node
				ArrayList<Connection> possibleActivations = new ArrayList<Connection>();
				for (Connection conn : connectionGenes) {
					if (conn.to.type != Node.INHIBITING_SEQUENCE
							&& conn.enabled) {
						possibleActivations.add(conn);
					}
				}
				if (possibleActivations.size() == 0) {
					mutationInfo = "Cannot add node";
				} else {
					int index = rand.nextInt(possibleActivations.size());
					Connection connection = possibleActivations.get(index);
					String inhibitionNodeName = "I" + connection.from.name
							+ connection.to.name;
					Node inhibitionNode = getNode(inhibitionNodeName);
					if (inhibitionNode == null) {
						inhibitionNode = new Node(inhibitionNodeName,
								config.inhK.defaultValue,
								config.seqC.defaultValue,
								Node.INHIBITING_SEQUENCE,
								connection.from.name,
								connection.to.name);
						nodeGenes.add(inhibitionNode);
						//also update the inhk and seqC, if relevant
						int where = 0;
						int nbSimpleSequence =((Integer) annotations.get("nbSimpleSequences"));
						if(this.config.seqK.optimizeMechanism == RunConfig.SEQK_OPTIMIZE_YES){
							where+= nbSimpleSequence;
						}
						if(this.config.inhK.optimizeMechanism == RunConfig.INHK_OPTIMIZE_YES){
							genes[where+nbSimpleSequence*nametoint.get(connection.from.name)+nametoint.get(connection.to.name)] = config.inhK.defaultValue;
							where+= nbSimpleSequence*nbSimpleSequence;
						}
						if(this.config.seqC.optimizeMechanism == RunConfig.SEQC_OPTIMIZE_YES){
							genes[where+nbSimpleSequence*(nametoint.get(connection.from.name)+1)+nametoint.get(connection.to.name)] = config.seqC.defaultValue;
						}
					}
					int i = nodeNames.size() + 99;
					char c = (char) i;
					String nodeName = Character.toString(c);
					String nodeOrigins = "null->" + inhibitionNode.name;
					if (nodeNames.get(nodeOrigins) == null) {
						nodeNames.put(nodeOrigins, nodeName);
					} else {
						nodeName = nodeNames.get(nodeOrigins);
					}
					boolean hasNodeName = true;
					i = 0;
					while (hasNodeName) {
						if (i > 0) {
							nodeName = nodeName + String.valueOf(i);
						}
						hasNodeName = false;
						for (Node node : nodeGenes) {
							if (node.name.equals(nodeName)) {
								hasNodeName = true;
								break;
							}
						}
						i++;
					}
					Node newNode = new Node(nodeName, config.seqK.defaultValue,
							config.seqC.defaultValue, Node.SIMPLE_SEQUENCE);
					nodeGenes.add(newNode);
					this.genes = increasedSequenceNumber();
					nametoint.put(nodeName, ((Integer) annotations.get("nbSimpleSequences")-1));
					inttoname.put(((Integer) annotations.get("nbSimpleSequences")-1), nodeName);
					addConnection(newNode, inhibitionNode, true,
							config.template.defaultValue);
					addConnection(newNode, newNode, true,
							config.template.defaultValue);
					mutationInfo = "Mutate Add Node :" + newNode.name + "->"
							+ inhibitionNode.name;
				}
			}

		} else if (connectionGenes.size() > 0) {
			ArrayList<Connection> possibleActivations = new ArrayList<Connection>();
			for (Connection conn : connectionGenes) {
				if (conn.enabled) {
					possibleActivations.add(conn);
				}
			}
			if (possibleActivations.size() == 0) {
				mutationInfo = "Cannot Add Node";
			} else {
				Connection connection = null;

				int index = rand.nextInt(possibleActivations.size());
				connection = getCorrectConnection(possibleActivations
						.get(index));

				connection.enabled = false;
				String nodeOrigins = connection.from.name + "->"
						+ connection.to.name;
				int i = nodeNames.size() + 99;
				char c = (char) i;
				String nodeName = Character.toString(c);
				if (nodeNames.get(nodeOrigins) == null) {
					nodeNames.put(nodeOrigins, nodeName);
				} else {
					nodeName = nodeNames.get(nodeOrigins);
				}
				boolean hasNodeName = true;
				i = 0;
				while (hasNodeName) {
					if (i > 0) {
						nodeName = nodeName + String.valueOf(i);
					}
					hasNodeName = false;
					for (Node node : nodeGenes) {
						if (node.name.equals(nodeName)) {
							hasNodeName = true;
							break;
						}
					}
					i++;
				}
				Node newNode = new Node(nodeName, config.seqK.defaultValue,
						config.seqC.defaultValue, Node.SIMPLE_SEQUENCE);
				nodeGenes.add(newNode);
				this.genes = increasedSequenceNumber();
				nametoint.put(nodeName, ((Integer) annotations.get("nbSimpleSequences")-1));
				inttoname.put(((Integer) annotations.get("nbSimpleSequences")-1), nodeName);
				// double firstParam = rand.nextDouble() * connection.parameter;
				// double secondParam = connection.parameter - firstParam;
				if (connection.to.type == Node.INHIBITING_SEQUENCE) {
					addConnection(connection.from, newNode, true,
							config.template.defaultValue);
				} else {
					addConnection(connection.from, newNode, true,
							connection.parameter);
				}
				addConnection(newNode, connection.to, true,
						connection.parameter);
				String oldNodeName = "I" + connection.from.name
						+ connection.to.name;
				for (Node node : nodeGenes) {
					if (node.name.equals(oldNodeName)) {
						node.name = "I" + connection.from.name + newNode.name;
						for (Connection conn : connectionGenes) {
							if (conn.to == node) {
								int innovation = innovations.size() + 1;
								String key = conn.from.name + "->"
										+ conn.to.name;
								if (innovations.get(key) == null) {
									innovations.put(key, innovation);
								} else {
									innovation = innovations.get(key);
								}
								conn.innovation = innovation;
							}
						}
						break;
					}
				}
				mutationInfo = "Mutate Add Node :" + connection.from.name
						+ "->" + newNode.name + "->" + connection.to.name;
			}
		}
		return mutationInfo;
	}

	protected double[] increasedSequenceNumber() {
		int nbSimpleSequences = ((Integer) annotations.get("nbSimpleSequences")) +1;
		annotations.put("nbSimpleSequences", nbSimpleSequences);
		int size = nbSimpleSequences*nbSimpleSequences*(nbSimpleSequences+1); //Templates
		if(config.seqK.optimizeMechanism == RunConfig.SEQK_OPTIMIZE_YES){
			size += nbSimpleSequences;
		}
		if(config.inhK.optimizeMechanism == RunConfig.INHK_OPTIMIZE_YES){
			size += nbSimpleSequences*nbSimpleSequences;
		}
		if(config.seqC.optimizeMechanism == RunConfig.SEQC_OPTIMIZE_YES){
			size += nbSimpleSequences*(nbSimpleSequences+1);
		}
		if(config.exoActivity.optimizeMechanism == RunConfig.EXO_OPTIMIZE_YES){
			size += 1;
		}
		if(config.exoSimpleKm.optimizeMechanism == RunConfig.EXO_OPTIMIZE_YES){
			size += 1;
		}
		if(config.exoInhibKm.optimizeMechanism == RunConfig.EXO_OPTIMIZE_YES){
			size += 1;
		}
		if(config.polActivity.optimizeMechanism == RunConfig.EXO_OPTIMIZE_YES){
			size += 1;
		}
		if(config.polKm.optimizeMechanism == RunConfig.EXO_OPTIMIZE_YES){
			size += 1;
		}
		if(config.polDoubleStrandKm.optimizeMechanism == RunConfig.EXO_OPTIMIZE_YES){
			size += 1;
		}
		if(config.nickActivity.optimizeMechanism == RunConfig.EXO_OPTIMIZE_YES){
			size += 1;
		}
		if(config.nickKm.optimizeMechanism == RunConfig.EXO_OPTIMIZE_YES){
			size += 1;
		}
		double[] ret = new double[size];
		int wherenew = 0;
		int whereold = 0;
		if(config.seqK.optimizeMechanism == RunConfig.SEQK_OPTIMIZE_YES){
			for(int i=0; i < nbSimpleSequences - 1; i++){
				ret[i] = genes[i];
			}
			ret[nbSimpleSequences - 1] = config.seqK.defaultValue;
			wherenew += nbSimpleSequences;
			whereold += nbSimpleSequences-1;
		}
		if(config.inhK.optimizeMechanism == RunConfig.INHK_OPTIMIZE_YES){
			for(int i=0; i < nbSimpleSequences - 1; i++){
				for(int j=0; j< nbSimpleSequences - 1; j++){
					ret[wherenew + nbSimpleSequences*i + j] = genes[whereold + (nbSimpleSequences-1)*i + j];
				}
				ret[wherenew + nbSimpleSequences*(i+1) - 1] = 0; //last collumn on each line
			}
			//new line
			for(int i=0; i < nbSimpleSequences; i++){
				ret[wherenew + nbSimpleSequences*(nbSimpleSequences - 1) + i] = 0;
			}
			wherenew += nbSimpleSequences*nbSimpleSequences;
			whereold += (nbSimpleSequences-1)*(nbSimpleSequences-1);
		}
		if(config.seqC.optimizeMechanism == RunConfig.SEQC_OPTIMIZE_YES){
			for(int i=0; i < nbSimpleSequences; i++){
				for(int j=0; j< nbSimpleSequences - 1; j++){
					ret[wherenew + nbSimpleSequences*i + j] = genes[whereold + (nbSimpleSequences-1)*i + j];
				}
				ret[wherenew + nbSimpleSequences*(i+1) - 1] = config.seqC.defaultValue; //last collumn on each line
			}
			//new line
			for(int i=0; i < nbSimpleSequences; i++){
				ret[wherenew + nbSimpleSequences*(nbSimpleSequences) + i] = config.seqC.defaultValue;
			}
			wherenew += nbSimpleSequences*(nbSimpleSequences+1);
			whereold += (nbSimpleSequences-1)*(nbSimpleSequences);
		}
		
		//transferring templates
		for(int i=0; i<nbSimpleSequences;i++){
			for(int j=0; j<nbSimpleSequences-1;j++){
				for(int k=0; k<nbSimpleSequences-1; k++){
					ret[wherenew + nbSimpleSequences*nbSimpleSequences*i+nbSimpleSequences*j+k] = this.genes[whereold + (nbSimpleSequences-1)*(nbSimpleSequences-1)*i+(nbSimpleSequences-1)*j+k];		
				}
				ret[wherenew + nbSimpleSequences*nbSimpleSequences*i+nbSimpleSequences*(j+1)-1] = 0;
			}
			//complete the last (new) line
			for(int j=0; j<nbSimpleSequences; j++){
				ret[wherenew + nbSimpleSequences*nbSimpleSequences*(i+1) -nbSimpleSequences + j] = 0;
			}
		}
		//now complete the last (new) square
		for(int j=0; j<nbSimpleSequences-1;j++){
			for(int k=0; k<nbSimpleSequences-1; k++){
				ret[wherenew + nbSimpleSequences*nbSimpleSequences*nbSimpleSequences+nbSimpleSequences*j+k] = 0;		
			}
			ret[wherenew + nbSimpleSequences*nbSimpleSequences*nbSimpleSequences+nbSimpleSequences*(j+1)-1] = 0;
		}
		//complete the last (new) line
		for(int j=0; j<nbSimpleSequences; j++){
			ret[wherenew + nbSimpleSequences*nbSimpleSequences*(nbSimpleSequences+1) -nbSimpleSequences + j] = 0;
		}
		wherenew += (nbSimpleSequences+1)*nbSimpleSequences*nbSimpleSequences;
		whereold += (nbSimpleSequences-1)*(nbSimpleSequences-1)*nbSimpleSequences;
		//complete with the enzymes, if any
		for(int i=0; i < size - wherenew; i++){
			ret[wherenew +i] = genes[whereold+i];
		}
		return ret;
	}

	@Override
	public void mutate() {
		if (rand.nextDouble() < Constants.probParamMutation
				&& connectionGenes.size() > 0) {
			this.mutateParameter();
		} else if (rand.nextDouble() < Constants.probAddNodeMutation) {
			this.mutateAddNode();
		} else {
			this.mutateAddConnection();
		}
	}

	public JPanel printModelInTextArea() {
		JPanel info = new JPanel();
		JTabbedPane tabbedPane = new JTabbedPane();
		info.add(tabbedPane);
		JTextPane txtPane = new JTextPane();
		String text = "";
		text += this.toString();
		if (this.fitnessResult != null) {
			text += "Fitness: " + this.fitnessResult.toString() + "\n";
		}

		text += "Mutation: " + this.mutationInfo + "\n";
		if (this.spicies != null) {
			text += "Species: " + this.spicies.generationIndex + ","
					+ this.spicies.individualIndex + "\n";
		}
		if (this.indivCompared != null) {
			text += "Indiv compared: " + this.indivCompared.generationIndex
					+ "," + this.indivCompared.individualIndex + "\n";
		}
		txtPane.setText(text);

		JTextPane modelString = new JTextPane();
		modelString.setText(this.modelString);
		tabbedPane.addTab("NEAT", txtPane);
		tabbedPane.addTab("NATH", modelString);
		return info;
	}

	protected Node getFirstRedundantNode() {
		for (Node node : nodeGenes) {
			boolean foundConnectionTo = false;
			for (Connection conn : connectionGenes) {
				if (conn.enabled && conn.to == node) {
					foundConnectionTo = true;
					break;
				}
			}
			if (!foundConnectionTo) {
				return node;
			}
		}
		return null;
	}

	private void removeNode(Node node) {
		for (int j = connectionGenes.size() - 1; j >= 0; j--) {
			Connection conn = connectionGenes.get(j);
			if (conn.to == node || conn.from == node) {
				connectionGenes.remove(conn);
			}
		}
		nodeGenes.remove(node);
		int index = nametoint.get(node.name);
		nametoint.remove(node.name);
		inttoname.remove(index);
	}

	public Individual getMinimalStructure() throws CloneNotSupportedException {
		Individual indiv = (Individual) this.clone();
		while (indiv.getFirstRedundantNode() != null
				&& indiv.getNSimpleSequences() > 1) {
			indiv.removeNode(indiv.getFirstRedundantNode());
		}
		return indiv;
	}



	public JPanel displayTimeSereis() {
		OligoSystemGeneral oligoModel = getOligoSystem();
		double[][] data = oligoModel.getTimeSeriesComplexModel();
		String[] seqNames = new String[data.length];
		for (int i = 0; i < data.length; i++) {
			seqNames[i] = oligoModel.intToName.get(i);
			if (seqNames[i] == null)
				seqNames[i] = String.valueOf(i);
		}
		final PlotExpData demo = new PlotExpData("Generation "
				+ this.generationIndex + " Index " + this.individualIndex,
				data, seqNames, "Time", "Expression");
		return (demo);
	}


	public int getNSimpleSequences() {
		int nSimpleSequences = 0;
		for (Node node : this.nodeGenes) {
			if (node.type == Node.SIMPLE_SEQUENCE) {
				nSimpleSequences++;
			}
		}
		return nSimpleSequences;
	}

	public double getFitness() {
		if (this.fitnessResult != null) {
			return this.fitnessResult.finalResult();
		} else {
			return 0;
		}
	}

	public String getFitnessString() {
		if (this.fitnessResult != null) {
			return this.fitnessResult.toString();
		} else {
			return "null";
		}
	}

	public int getNumberOfTemplates() {
		int result = 0;
		for (Connection c : connectionGenes) {
			if (c.enabled)
				result++;
		}
		return result;
	}

	public double getStandardDeviationOfLogK() {
		int count = 0;
		double sum = 0;
		for (Node node : nodeGenes) {
			if (node.type == Node.SIMPLE_SEQUENCE) {
				sum += Math.log(node.parameter);
				count++;
			}
		}
		double avg = sum / count;
		double sd = 0;
		for (Node node : nodeGenes) {
			if (node.type == Node.SIMPLE_SEQUENCE) {
				sd += (Math.log(node.parameter) - avg)
						* (Math.log(node.parameter) - avg);
			}
		}
		sd = Math.sqrt(sd / count);
		return sd;
	}

	public String printSimpleModel() {
		StringBuilder builder = new StringBuilder();
		for (Connection connection : connectionGenes) {
			if (connection.enabled) {
				builder.append("(" + connection.from.name + connection.to.name
						+ ")");
			}
		}
		return builder.toString();
	}
	
	public boolean checkIntegrity(){
		//TODO: code this. Supposed to check that the internal representation 
		//of the Individual and its genes represent the same thing
		
		//First, check the size sanity.
		
		//Number of simple sequences
		int nbOfSimpleSeq = 0;
		for(Node node : this.nodeGenes){
			if(node.type == Node.SIMPLE_SEQUENCE){
				nbOfSimpleSeq++;
			}
		}
		if(!((Integer) this.annotations.get("nbSimpleSequences")).equals(nbOfSimpleSeq)){
			System.out.println("Size discrepency "+nbOfSimpleSeq+" "+(Integer) this.annotations.get("nbSimpleSequences"));
			return false;
		}
		return true;
	}

	public static void main(String[] args){
		HashMap<String,Object> annotations = new HashMap<String,Object>();
		annotations.put("generationIndex", 0);
		annotations.put("individualIndex", 0);
		Individual indiv = new Individual(new double[]{0,0},2, new SinConfiguration(), annotations);
		indiv.checkIntegrity();
	}
}
